<?php // $Id: logout.php,v 1.1.2.1 2008/12/04 16:27:00 exe-cutor Exp $

// Implements logout for Shibboleth authenticated users according to:
// - https://spaces.internet2.edu/display/SHIB2/NativeSPLogoutInitiator
// - https://spaces.internet2.edu/display/SHIB2/NativeSPNotify

require_once("../../config.php");

require_once($CFG->dirroot."/auth/shibboleth/auth.php");


// Front channel logout
if (
		isset($_GET['return']) 
		&& isset($_GET['action'])
		&& $_GET['action'] == 'logout'
   ){
	
	// Logout out user from application
	// E.g. destroy application session/cookie etc
	require_logout();
	
	// Finally, send user to the return URL
	redirect($_GET['return']);
}

// Back channel logout
elseif (!empty($HTTP_RAW_POST_DATA)) {
	
	// Requires PHP 5
	
	// Set SOAP header
	$server = new SoapServer('https://'.$_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF'].'/LogoutNotification.wsdl');
	$server->addFunction("LogoutNotification");
	$server->handle();
} 

// Return WSDL
else {
	
	header('Content-Type: text/xml');
	
	echo <<<WSDL
<?xml version ="1.0" encoding ="UTF-8" ?>
<definitions name="LogoutNotification"
  targetNamespace="urn:mace:shibboleth:2.0:sp:notify"
  xmlns:notify="urn:mace:shibboleth:2.0:sp:notify"
  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
  xmlns="http://schemas.xmlsoap.org/wsdl/">

<!--
This page either has to be called with the GET arguments 'action' and 'return' via
a redirect from the Shibboleth Service Provider logout handler (front-channel 
logout) or via a SOAP request by a Shibboleth Service Provider (back-channel 
logout).
Because neither of these two variants seems to be the case, the WSDL file for 
the web service is returned.

For more information see:
- https://spaces.internet2.edu/display/SHIB2/NativeSPLogoutInitiator
- https://spaces.internet2.edu/display/SHIB2/NativeSPNotify
-->

	<types>
	   <schema targetNamespace="urn:mace:shibboleth:2.0:sp:notify"
		   xmlns="http://www.w3.org/2000/10/XMLSchema"
		   xmlns:notify="urn:mace:shibboleth:2.0:sp:notify">
			
			<simpleType name="string">
				<restriction base="string">
					<minLength value="1"/>
				</restriction>
			</simpleType>
			
			<element name="OK" type="notify:OKType"/>
			<complexType name="OKType">
				<sequence/>
			</complexType>
			
		</schema>
	</types>
	
	<message name="getLogoutNotificationRequest">
		<part name="SessionID" type="notify:string" />
	</message>
	
	<message name="getLogoutNotificationResponse" >
		<part name="OK"/>
	</message>
	
	<portType name="LogoutNotificationPortType">
		<operation name="LogoutNotification">
			<input message="getLogoutNotificationRequest"/>
			<output message="getLogoutNotificationResponse"/>
		</operation>
	</portType>
	
	<binding name="LogoutNotificationBinding" type="notify:LogoutNotificationPortType">
		<soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
		<operation name="LogoutNotification">
			<soap:operation soapAction="urn:xmethods-logout-notification#LogoutNotification"/>
		</operation>
	</binding>
	
	<service name="LogoutNotificationService">
		  <port name="LogoutNotificationPort" binding="notify:LogoutNotificationBinding">
			<soap:address location="https://{$_SERVER['HTTP_HOST']}{$_SERVER['PHP_SELF']}"/>
		  </port>
	</service>
</definitions>
WSDL;
	exit;

}

/******************************************************************************/

function LogoutNotification($SessionID){
	
	global $CFG, $SESSION;
	
	// Delete session of user using $SessionID
	if(empty($CFG->dbsessions)) {
		
		// File session
		$dir = $CFG->dataroot .'/sessions';
		if (is_dir($dir)) {
			if ($dh = opendir($dir)) {
				while (($file = readdir($dh)) !== false) {
					//echo $dir.'/'.$file."\n";exit;
					if (is_file($dir.'/'.$file)){
						$session_key = ereg_replace('sess_', '', $file);
						
						$data = file($dir.'/'.$file);
					   if (isset($data[0])){
							$user_session = unserializesession($data[0]);
							
							if (isset($user_session['SESSION']) && isset($user_session['SESSION']->shibboleth_session_id)){
								//echo '2. Shibboleth Session (from filesystem session) of '.$user_session['USER']->username.':' .$user_session['SESSION']->shibboleth_session_id."\n";
								// If there is a match, delete file
								if ($user_session['SESSION']->shibboleth_session_id == $SessionID){
									// Delete this file
									if (!unlink($dir.'/'.$file)){
										return new SoapFault('LogoutError', 'Could not delete Moodle session file.');
									}
								}
							}
							//print_r($user_session);
						}
						
						//echo "Moodle session: $session_key \n";
						//echo "filename: $file \n";
					}
				}
				closedir($dh);
			}
		}
	} else {
		// DB Session
		if (!empty($CFG->sessiontimeout)) {
			$ADODB_SESS_LIFE   = $CFG->sessiontimeout;
		}
		
			if ($user_session_data = get_records_sql('SELECT sesskey, sessdata FROM '. $CFG->prefix .'sessions2 WHERE expiry > NOW()')) {
			foreach ($user_session_data as $session_data) {
				
				//print_r($session_data);
				$user_session = adodb_unserialize( urldecode($session_data->sessdata) );
				
				if (isset($user_session['SESSION']) && isset($user_session['SESSION']->shibboleth_session_id)){
					//echo '3. Shibboleth Session (from ADODB session) of '.$user_session['USER']->username.':' .$user_session['SESSION']->shibboleth_session_id."\n";
					
					// If there is a match, delete file
					if ($user_session['SESSION']->shibboleth_session_id == $SessionID){
						// Delete this session entry
						if (ADODB_Session::destroy($session_data->sesskey) !== true){
							return new SoapFault('LogoutError', 'Could not delete Moodle session entry in database.');
						}
					}
				}
				
				//print_r($user_session);
			}
		}
	}
	
	// If now SoapFault was thrown the function will return OK as the SP assumes
	
}

/*****************************************************************************/

// Same function as in adodb, but cannot be used for file session for some reason...
function unserializesession( $serialized_string ){
	$variables = array( );
	$a = preg_split( "/(\w+)\|/", $serialized_string, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE );
	for( $i = 0; $i < count( $a ); $i = $i+2 ) {
			$variables[$a[$i]] = unserialize( $a[$i+1] );
	}
	return( $variables );
}


?>
